/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "event.h"
#include "list.h"
#include "circular_buf.h"
#include "spinlock.h"
#include "os_api.h"
#include "os_error.h"
#include "errno-base.h"

#define log_e   printf
#define log_d   printf

#define bool BOOL
#define EVENT_HANDLER_NUM_CONFIG    (2)
#define EVENT_POOL_SIZE_CONFIG      (256)

#define list_for_each_event_handler(p) \
    list_for_each_entry(p, &__this->event_head, entry)

struct sys_key_map {
    struct key_event org;
    struct key_event new;
    struct list_head entry;
};

struct event_handler {
    struct list_head entry;
    void *priv;
    void (*handler)(struct sys_event *);
    const char *owner;
    u8 del;
    u8 priority;
    u16 event_type;
    int from;
};

struct sys_event_handl {
    u8 inited;
    u8 takeover_type;
    u8 takeover_off_once;
    u8 key_disable;
    u8 key_lock;
    u8 key_filter;
    u8 key_filter_enable;
#if EVENT_TOUCH_ENABLE_CONFIG
    u8 touch_disable;
    u8 touch_lock;
    u8 touch_filter;
#endif
    int sem_timeout;
    const char *takeover_task;
    cbuffer_t     *event_pool;
    int *sem;
    spinlock_t  lock;
    struct list_head event_head;
#if CONFIG_EVENT_KEY_MAP_ENABLE
    spinlock_t  map_lock;
    struct list_head map;
    struct list_head map_free;
#endif
};

static struct sys_event_handl handl = {
    .inited = 0,
    .key_disable = 1,
    .key_filter_enable = 1,
#if EVENT_TOUCH_ENABLE_CONFIG
    .touch_disable = 1,
#endif
    .sem_timeout = 40000,
};

#define __this (&handl)

static int event_pool_init()
{
    __this->event_pool = malloc(sizeof(cbuffer_t) + EVENT_POOL_SIZE_CONFIG);
    if (!__this->event_pool) {
        return -ENOMEM;
    }

    cbuf_init(__this->event_pool, __this->event_pool + 1, EVENT_POOL_SIZE_CONFIG);

    return 0;
}

static void __event_to_task(struct sys_event *event, void *name)
{
    task_queue_post_event(name, event, sizeof(struct sys_event));
}

int __attribute__((weak)) sys_event_recode(struct sys_event *event)
{
    return 0;
}

void sys_event_timeout_set(int timeout)
{
    __this->sem_timeout = timeout;
}

int sys_event_timeout_get(void)
{
    return __this->sem_timeout;
}

/* 事件发布到事件列表 */
void sys_event_notify(struct sys_event *event)
{
    u32 rets_addr;
    __asm__ volatile("%0 = rets ;" : "=r"(rets_addr));
    int len;
    struct sys_key_map *p;

    if (!__this->event_pool) {
        if (event_pool_init()) {
            return;
        }
    }

    if (event->type == SYS_KEY_EVENT) {
#if CONFIG_EVENT_KEY_MAP_ENABLE
        spin_lock(&__this->map_lock);
        list_for_each_entry(p, &__this->map, entry) {
            if (event->u.key.event == p->org.event &&
                event->u.key.value == p->org.value) {
                event->u.key.event = p->new.event;
                event->u.key.value = p->new.value;
                break;
            }
        }
        spin_unlock(&__this->map_lock);
#endif

        spin_lock(&__this->lock);

        if (__this->key_disable) {
            if (event->u.key.event == KEY_EVENT_LONG) {
                __this->key_filter = event->u.key.value;
                spin_unlock(&__this->lock);
                return;
            }
            if (event->u.key.event != KEY_EVENT_UP ||
                __this->key_filter == event->u.key.value) {
                spin_unlock(&__this->lock);
                return;
            }
        } else if (__this->key_lock) {
            if (event->u.key.event == KEY_EVENT_LONG) {
                __this->key_filter = event->u.key.value;
                spin_unlock(&__this->lock);
                return;
            }
            if (event->u.key.event != KEY_EVENT_UP &&
                __this->key_filter == event->u.key.value) {
                spin_unlock(&__this->lock);
                return;
            }
        }
        __this->key_filter = 0xff;
        spin_unlock(&__this->lock);

    }

#if EVENT_TOUCH_ENABLE_CONFIG
    else if (event->type == SYS_TOUCH_EVENT) {
        spin_lock(&__this->lock);
        if (__this->touch_disable || __this->touch_lock) {
            if (event->u.touch.event == TOUCH_EVENT_DOWN) {
                __this->touch_filter = 1;
                spin_unlock(&__this->lock);
                return;
            }
        }
        if (__this->touch_filter) {
            if (event->u.touch.event != TOUCH_EVENT_DOWN) {
                spin_unlock(&__this->lock);
                return;
            }
            __this->touch_filter = 0;
        }
        spin_unlock(&__this->lock);
    }
#endif

    if (sys_event_recode(event)) {
        return;
    }

    u8 retry = 10;      // retry cnt, retry >= 1 at less
    while (1) {
        len = cbuf_write(__this->event_pool, event, sizeof(*event));
        printf(">>>cbuf_write, len = %d, remain = %d", len, cbuf_get_data_size(__this->event_pool));
        if (len != sizeof(*event)) {
            retry--;
            if (retry == 0 || cpu_in_irq() || cpu_irq_disabled()) {
                log_e("event_lose rets:%x, %d, %d, %d\n", rets_addr, retry, cpu_in_irq(), cpu_irq_disabled());
                break;
            } else {
                os_time_dly(1);
            }
        } else {
            // succ
            break;
        }
    }

    if (__this->sem) {
        os_sem_post(__this->sem);
    }
}

void sys_event_clear(struct sys_event *e)
{
    int len;
    int data_len;
    struct sys_event event;

    spin_lock(&__this->lock);

    data_len = cbuf_get_data_size(__this->event_pool);

    while (data_len) {
        len = cbuf_read(__this->event_pool, &event, sizeof(event));
        if (len == 0) {
            break;
        }
        data_len -= len;
        if (event.type == e->type && event.arg == e->arg) {
            continue;
        }
        cbuf_write(__this->event_pool, &event, sizeof(event));
    }

    spin_unlock(&__this->lock);
}

/* handle注册函数 */
int register_sys_event_handler(int event_type, int from, u8 priority,
                               void (*handler)(struct sys_event *))
{
    struct event_handler *p, *e;

    if (!__this->inited) {
        if (sys_event_init()) {
            return -ENOMEM;
        }
    }

    list_for_each_entry(p, &__this->event_head, entry) {
        if (p->del) {
            spin_lock(&__this->lock);
            __list_del_entry(&p->entry);
            spin_unlock(&__this->lock);
            goto __get;
        }
    }

    p = (struct event_handler *)malloc(sizeof(*p));
    if (!p) {
        return -ENOMEM;
    }

__get:
    p->del = 0;
    p->from = from;
    p->handler = handler;
    p->event_type = event_type;
    p->priority = priority;
    p->owner = os_current_task();

    ASSERT(p->owner, "unknow_task\n");

    /*
     * 按优先级大小排序
     */
    spin_lock(&__this->lock);

    list_for_each_event_handler(e) {
        if (e->priority < priority) {
            __list_add(&p->entry, e->entry.prev, &e->entry);
            goto _exit;
        }
    }

    list_add_tail(&p->entry, &__this->event_head);
_exit:
    spin_unlock(&__this->lock);

    return 0;
}

/* handle注销函数 */
void unregister_sys_event_handler(void (*handler)(struct sys_event *))
{
    struct event_handler *p;

    spin_lock(&__this->lock);

    list_for_each_event_handler(p) { /* 去除一个handler一样的节点 */
        if (p->handler == handler) {
            p->del = 1;
            break;
        }
    }

    spin_unlock(&__this->lock);
}

void sys_key_event_consume(struct key_event *e)
{
    struct sys_event *event = container_of(e, struct sys_event, u.key);

    ASSERT(event->type == SYS_KEY_EVENT);

    event->consumed = 1;
}

#if EVENT_TOUCH_ENABLE_CONFIG
void sys_touch_event_consume(struct touch_event *e)
{
    struct sys_event *event = container_of(e, struct sys_event, u.touch);

    ASSERT(event->type == SYS_TOUCH_EVENT);

    event->consumed = 1;
}
#endif

void sys_device_event_consume(struct device_event *e)
{
    struct sys_event *event = container_of(e, struct sys_event, u.dev);

    ASSERT(event->type == SYS_DEVICE_EVENT);

    event->consumed = 1;
}

static void sys_event_takeover(bool on, bool once)
{
    if (on) {
        __this->takeover_off_once = 0;
        __this->takeover_task = os_current_task();
    } else {
        if (once) {
            __this->takeover_off_once = true;
        } else {
            __this->takeover_task = NULL;
        }
    }
}

void sys_key_event_takeover(bool on, bool once)
{
    sys_event_takeover(on, once);
    __this->takeover_type = SYS_KEY_EVENT;
}

#if EVENT_TOUCH_ENABLE_CONFIG
void sys_touch_event_takeover(bool on, bool once)
{
    sys_event_takeover(on, once);
    __this->takeover_type = SYS_TOUCH_EVENT;
}
#endif

void sys_key_event_disable()
{
    int len;
    int data_len;
    int clear_key_up = 0xff;
    struct sys_event event;

    spin_lock(&__this->lock);
    __this->key_disable = 1;
    __this->key_filter = 0xff;

    data_len = cbuf_get_data_size(__this->event_pool);

    while (data_len) {
        len = cbuf_read(__this->event_pool, &event, sizeof(event));
        if (len == 0) {
            break;
        }
        data_len -= len;
        if (event.type == SYS_KEY_EVENT) {
            if (event.u.key.event == KEY_EVENT_LONG) {
                clear_key_up = event.u.key.value;
            } else if (event.u.key.event == KEY_EVENT_UP) {
                if (clear_key_up != event.u.key.value) {
                    goto __write;
                }
            }
            continue;
        }
__write:
        cbuf_write(__this->event_pool, &event, sizeof(event));
    }

    spin_unlock(&__this->lock);
}

void sys_key_event_enable()
{
    spin_lock(&__this->lock);
    __this->key_disable = 0;
    spin_unlock(&__this->lock);
}

void sys_key_event_filter_disable()
{
    spin_lock(&__this->lock);
    __this->key_filter_enable = 0;
    spin_unlock(&__this->lock);
}

void sys_key_event_filter_enable()
{
    spin_lock(&__this->lock);
    __this->key_filter_enable = 1;
    spin_unlock(&__this->lock);
}

#if EVENT_TOUCH_ENABLE_CONFIG
void sys_touch_event_disable()
{
    spin_lock(&__this->lock);
    __this->touch_filter = 0;
    __this->touch_disable = 1;
    spin_unlock(&__this->lock);
}

void sys_touch_event_enable()
{
    spin_lock(&__this->lock);
    __this->touch_disable = 0;
    spin_unlock(&__this->lock);
}
#endif

#if CONFIG_EVENT_KEY_MAP_ENABLE
static struct sys_key_map *__get_map()
{
    struct sys_key_map *map;

    spin_lock(&__this->map_lock);

    if (!list_empty(&__this->map_free)) {
        map = list_first_entry(&__this->map_free, struct sys_key_map, entry);
        list_del(&map->entry);
    } else {
        spin_unlock(&__this->map_lock);
        map = (struct sys_key_map *)malloc(sizeof(struct sys_key_map));
        if (!map) {
            return NULL;
        }
        spin_lock(&__this->map_lock);
    }

    list_add_tail(&map->entry, &__this->map);

    spin_unlock(&__this->map_lock);

    return map;
}

static void __put_map(struct sys_key_map *map)
{
    spin_lock(&__this->map_lock);

    list_del(&map->entry);
    list_add_tail(&map->entry, &__this->map_free);

    spin_unlock(&__this->map_lock);
}

int sys_key_event_map(struct key_event *org, struct key_event *new)
{
    struct sys_key_map *map;

    map = __get_map();
    if (!map) {
        return -ENOMEM;
    }
    map->org.event = org->event;
    map->org.value = org->value;
    map->new.event = new->event;
    map->new.value = new->value;

    return 0;
}

int sys_key_event_unmap(struct key_event *org, struct key_event *new)
{
    struct sys_key_map *p;

    spin_lock(&__this->map_lock);

    list_for_each_entry(p, &__this->map, entry) {
        if (p->org.event == org->event && p->org.value == org->value &&
            p->new.event == new->event && p->new.value == new->value) {
            spin_unlock(&__this->map_lock);
            __put_map(p);
            return 0;
        }
    }

    spin_unlock(&__this->map_lock);

    return -EINVAL;
}

static void key_map_init()
{
    int i;
    struct sys_key_map *map;

    spin_lock_init(&__this->map_lock);
    INIT_LIST_HEAD(&__this->map);
    INIT_LIST_HEAD(&__this->map_free);

    map = (struct sys_key_map *)malloc(sizeof(struct sys_key_map) * 2);
    if (map) {
        for (i = 0; i < 2; i++) {
            list_add_tail(&map->entry, &__this->map_free);
            map++;
        }
    }
}
#endif

/* 事件分发处理函数 */
static int sem_wait;
static void sys_event_parse(struct sys_event *event)
{
    int msg[6];
    int ret = false;
    const char *takeover_task;
    struct event_handler *p, *n;
    struct static_event_handler *s;

    event->consumed = 0;

    list_for_each_static_event_handler(s) {
        if (s->event_type & event->type) {
            s->handler(event);
            if (event->consumed) {
                return;
            }
        }
    }

    takeover_task = __this->takeover_task;

    spin_lock(&__this->lock);

    list_for_each_entry_safe(p, n, &__this->event_head, entry) {
        if (p->del || !p->handler) {
            continue;
        }
        if (!(p->event_type & event->type)) {
            continue;
        }
        if (p->from && p->from != (u32)event->arg) {
            continue;
        }

        if (takeover_task && (__this->takeover_type & event->type)) {
            if (!__this->takeover_off_once) {
                if (strcmp(p->owner, takeover_task)) {
                    continue;
                }
            }
        }

        spin_unlock(&__this->lock);

        msg[0] = (int)p->handler;
        msg[1] = BIT(9) | 2;
        msg[2] = (int)event;
        msg[3] = (int)p->priv;
        msg[4] = (int)&sem_wait;
        while (!p->del) {
            int err = os_taskq_post_type(p->owner, Q_CALLBACK, 5, msg);
            if (err == OS_ERR_NONE) {
                err = os_sem_pend(&sem_wait, __this->sem_timeout / 10);
                if (err == OS_TIMEOUT) {
                    log_d("event: %s, %x\n", p->owner, p->handler);
                }
                ASSERT(err != OS_TIMEOUT, "task %s no response\n", p->owner);
                break;
            } else if (err != OS_Q_FULL) {
                break;
            }

            os_time_dly(2);
        }

        if (__this->takeover_off_once) {
            event->consumed = 0;
        }
        if (event->consumed) {
            return;
        }

        spin_lock(&__this->lock);

        if (!takeover_task && __this->takeover_task) {
            takeover_task = __this->takeover_task;
        }

        if (p->entry.next != &n->entry) {
            n = list_entry(p->entry.next, struct event_handler, entry);
        }
    }

    __this->takeover_off_once = 0;

    spin_unlock(&__this->lock);
}

static struct sys_event event;

static void sys_event_task(void *p)
{
    int len;
    int *sem = malloc(sizeof(int));
    ASSERT(sem);

    os_sem_create(sem, 0);
    __this->sem = sem;

    while (1) {
        spin_lock(&__this->lock);

        len = cbuf_read(__this->event_pool, &event, sizeof(event));
        if (len == 0) {
            spin_unlock(&__this->lock);
            os_sem_pend(__this->sem, 0);
        } else {
            if (event.type == SYS_KEY_EVENT) {
                __this->key_lock = __this->key_filter_enable;
            }
#if EVENT_TOUCH_ENABLE_CONFIG
            else if (event.type == SYS_TOUCH_EVENT) {
                if (event.u.touch.event == TOUCH_EVENT_DOWN) {
                    __this->touch_filter = 0;
                    __this->touch_lock = 1;
                }
            }
#endif
            spin_unlock(&__this->lock);

            sys_event_parse(&event); /* 处理事件 */

            spin_lock(&__this->lock);
            if (event.type == SYS_KEY_EVENT) {
                __this->key_lock = 0;
            }
#if EVENT_TOUCH_ENABLE_CONFIG
            else if (event.type == SYS_TOUCH_EVENT) {
                if (event.u.touch.event == TOUCH_EVENT_UP) {
                    __this->touch_lock = 0;
                }
            }
#endif
            spin_unlock(&__this->lock);
        }
    }
    free(__this->sem);
}

/* __BANK_INIT */
int sys_event_init(void)
{
    int i;
    struct event_handler *p;

    if (__this->inited) {
        return 0;
    }

    __this->inited = 1;
    __this->key_filter = 0xff;
    __this->takeover_task = NULL;
    spin_lock_init(&__this->lock);
    INIT_LIST_HEAD(&__this->event_head);

    p = (struct event_handler *)malloc(sizeof(*p) * EVENT_HANDLER_NUM_CONFIG);
    if (!p) {
        return -ENOMEM;
    }
    for (i = 0; i < EVENT_HANDLER_NUM_CONFIG; i++) {
        p->del = 1;
        p->handler = NULL;
        list_add_tail(&p->entry, &__this->event_head);
        p++;
    }

#if CONFIG_EVENT_KEY_MAP_ENABLE
    key_map_init();
#endif

    if (!__this->event_pool) {
        event_pool_init();
    }

    os_sem_create(&sem_wait, 0);
    task_create(sys_event_task, 0, "sys_event");

    printf(">>>>>>>>>>>>%s\n", __FUNCTION__);
    return 0;
}

